<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>Consumer Client (C) - 视频播放</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      margin: 20px;
    }

    #videoPlayer {
      background: #000;
    }

    #log {
      border: 1px solid #ccc;
      padding: 10px;
      height: 300px;
      overflow: auto;
      font-size: 14px;
      white-space: pre-wrap;
      word-break: break-word;
    }

    .controls {
      margin: 15px 0;
    }

    #status {
      font-weight: bold;
      color: #007bff;
      margin-bottom: 10px;
    }
  </style>
</head>

<body>
  <h1>消费者客户端 (C)</h1>
  <div id="status">等待连接...</div>
  <div class="controls">
    <label for="filePathInput">文件路径：</label>
    <input type="text" id="filePathInput" placeholder="请输入文件路径" style="width: 300px;">
    <button id="requestButton" disabled>请求文件</button>
  </div>
  <br>
  <br />

  <video id="video" width="640" height="360" controls autoplay muted style="background:#000;"></video>
  <h3>日志输出</h3>
  <pre id="log"></pre>

  <script>
    // 生成唯一 clientId（每个 C 端唯一）
    function uuidv4() {
      return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
        var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
        return v.toString(16);
      });
    }
    const clientId = uuidv4();

    // 简单的日志打印函数：既输出到控制台，也输出到页面中
    function logMsg(msg) {
      console.log(msg);
      const logArea = document.getElementById("log");
      logArea.textContent += (new Date()).toLocaleTimeString() + " - " + msg + "\n";
      // 自动滚动到底部
      logArea.scrollTop = logArea.scrollHeight;

      // 更新状态指示器
      if (msg.includes("连接") || msg.includes("打开") || msg.includes("传输") || msg.includes("错误")) {
        document.getElementById("status").textContent = msg;
      }
    }

    // 信令服务器地址（请根据实际情况修改）
    const signalingServerUrl = "ws://43.156.74.32:8090/ws";
    let ws;                // WebSocket 对象
    let pc;                // RTCPeerConnection 对象
    let filePathChannel;   // 用于发送文件路径的 data channel
    const video = document.getElementById('video');
    const statusDiv = document.getElementById('status');
    let mediaSource = new MediaSource();
    let sourceBuffer;
    let queue = [];
    let mimeCodec = 'video/mp4; codecs="avc1.42E01E"'; // baseline H264 in mp4

    video.src = URL.createObjectURL(mediaSource);

    mediaSource.addEventListener('sourceopen', onSourceOpen);

    function onSourceOpen() {
      sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
      sourceBuffer.mode = 'segments';
      sourceBuffer.addEventListener('updateend', appendFromQueue);
      connectWS();
    }

    function appendFromQueue() {
      if (queue.length && !sourceBuffer.updating) {
        sourceBuffer.appendBuffer(queue.shift());
      }
    }

    // 连接信令服务器，并注册身份为 C
    function connectSignaling() {
      logMsg("正在连接信令服务器...");
      ws = new WebSocket(signalingServerUrl);
      ws.onopen = () => {
        logMsg("已连接信令服务器");
        // 注册消息，告知服务器本客户端为消费者（C）
        const registerMsg = { type: "register", role: "C" };
        ws.send(JSON.stringify(registerMsg));
        // 连接成功后建立 PeerConnection
        createPeerConnection();
      };

      ws.onmessage = (event) => {
        let msg;
        try {
          msg = JSON.parse(event.data);
        } catch (e) {
          logMsg("信令消息解析失败: " + e);
          return;
        }
        // 处理信令消息
        if (msg.type === "answer") {
          logMsg("收到 answer");
          const answer = { type: "answer", sdp: msg.sdp };
          pc.setRemoteDescription(new RTCSessionDescription(answer))
            .then(() => logMsg("已设置远端描述 (answer)"))
            .catch(err => logMsg("设置远端描述失败: " + err));
        } else if (msg.type === "candidate") {
          const candidate = JSON.parse(msg.candidate);
          pc.addIceCandidate(candidate)
            .then(() => logMsg("成功添加 ICE candidate"))
            .catch(err => logMsg("添加 ICE candidate 失败: " + err));
        }
      };

      ws.onerror = (err) => {
        logMsg("WebSocket 错误: " + err);
      };

      ws.onclose = () => {
        logMsg("WebSocket 连接已关闭，尝试重新连接...");
        setTimeout(connectSignaling, 3000); // 3秒后尝试重连
      };
    }

    // 创建 RTCPeerConnection 对象，配置 ICE 处理、数据通道等
    function createPeerConnection() {
      const config = {
        iceServers: [
          {
            urls: ["stun:stun.l.google.com:19302"]
          }
        ]
      };
      logMsg("创建 PeerConnection...");
      pc = new RTCPeerConnection(config);

      // ICE 候选生成时
      pc.onicecandidate = (event) => {
        if (event.candidate) {
          const msg = {
            type: "candidate",
            candidate: JSON.stringify(event.candidate.toJSON()),
            from: clientId
          };
          ws.send(JSON.stringify(msg));
        } else {
          logMsg("ICE Candidate 收集完成");
        }
      };

      // ICE 连接状态变化
      pc.oniceconnectionstatechange = () => {
        logMsg("ICE connection state: " + pc.iceConnectionState);
        if (pc.iceConnectionState === 'disconnected' ||
          pc.iceConnectionState === 'failed' ||
          pc.iceConnectionState === 'closed') {
          logMsg("ICE 连接已断开或失败。");
        }
      };

      // PeerConnection 状态变化
      pc.onconnectionstatechange = () => {
        logMsg("Connection state change: " + pc.connectionState);
      };

      // 创建数据通道用于发送文件路径给 B
      filePathChannel = pc.createDataChannel("filePathChannel");
      filePathChannel.onopen = () => {
        logMsg("filePathChannel 已打开");
        // 数据通道打开后启用请求按钮
        document.getElementById("requestButton").disabled = false;
      };
      filePathChannel.onclose = () => {
        logMsg("filePathChannel 已关闭");
        document.getElementById("requestButton").disabled = true;
      };
      filePathChannel.onerror = (e) => {
        logMsg("filePathChannel 错误: " + e);
      };

      filePathChannel.onmessage = (event) => {
        if (event.data instanceof ArrayBuffer) {
          // 这里假设后端发来的是完整的 mp4 分片（fMP4）
          if (!sourceBuffer.updating) {
            sourceBuffer.appendBuffer(new Uint8Array(event.data));
          } else {
            queue.push(new Uint8Array(event.data));
          }
        }
      };

      // 创建本端 offer，并通过信令通道发送给 B
      pc.createOffer().then(offer => {
        return pc.setLocalDescription(offer);
      }).then(() => {
        logMsg("已设置本地描述");
        const msg = { type: "offer", sdp: pc.localDescription.sdp, from: clientId };
        ws.send(JSON.stringify(msg));
        logMsg("offer 发送成功");
      }).catch(error => {
        logMsg("创建 offer 过程中出错:" + error);
      });
    }

    // 点击请求按钮时，从输入框获取文件路径，并通过 filePathChannel 发送出去
    function requestFile() {
      const filePathInput = document.getElementById("filePathInput");
      const filePath = filePathInput.value.trim();
      if (!filePath) {
        alert("请输入文件路径");
        return;
      }

      // 发送文件路径请求
      filePathChannel.send(JSON.stringify(filePath));
      logMsg("发送文件路径请求: " + filePath);
    }

    document.getElementById("requestButton")
      .addEventListener("click", requestFile);

    // 页面加载时初始化连接
    window.onload = () => {
      connectSignaling();
    };
  </script>
</body>

</html>